import numpy as np
import pandas as pd
# 数据标准化预处理
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 数据集的切分
from sklearn.model_selection import train_test_split
# 评价模型预测效果
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
# 全连接网络的搭建和训练
import torch
import torch.nn as nn
import torch.utils.data as Data
# 可视化工具
import hiddenlayer as hl
from torchviz import make_dot

###############################1、垃圾邮件数据准备########################
spam = pd.read_csv("spambase.csv")
print(pd.value_counts(spam.iloc[:, 57]))  # 统计正常邮件和垃圾邮件的数量

# 将数据随机切分为训练集和测试集,75%为训练集，25%为测试集
X = spam.iloc[:, 0:57].values  # 特征值
Y = spam.iloc[:, 57].values  # spam的最后一列为标签
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.25, random_state=123)
# 对数据的前57列特征进行数据标准化处理
scales = MinMaxScaler(feature_range=(0, 1))
X_train_s = scales.fit_transform(X_train)
X_test_s = scales.transform(X_test)


# 数据特征可视化
# colname=spam.columns.values[:-1]
# plt.figure(figsize=(20,14))
# for ii in range(len(colname)):
#     plt.subplot(7,9,ii+1)
#     sns.boxplot(x=Y_train,y=X_train_s[:ii])
#     plt.title(colname[ii])
# plt.subplots_adjust(hspace=0.4)
# plt.show()

##############################2、搭建全连接神经网络###########################
class MLPclassifica(nn.Module):
    def __init__(self):
        super(MLPclassifica, self).__init__()
        # 定义第一个隐藏层
        self.hidden1 = nn.Sequential(
            nn.Linear(
                in_features=57,  # 第一个隐藏层的输入，数据的特征数
                out_features=30,  # 第一个隐藏层的输出，神经元的数量
                bias=True,  # 默认会有偏置
            ),
            nn.ReLU()

        )
        # 定义第二个隐藏层
        self.hidden2 = nn.Sequential(
            nn.Linear(30, 10),  # 30个输入（上一层），10个输出神经元
            nn.ReLU()
        )
        # 分类层
        self.classifica = nn.Sequential(
            nn.Linear(10, 2),  # 10个输入（上一层），2个输出神经元。二分类问题，故分类层有2个神经元
            nn.Sigmoid()
        )

        # 定义网络的前向传播路径
    def forward(self, x):
        fc1 = self.hidden1(x)
        fc2 = self.hidden2(fc1)
        output = self.classifica(fc2)
        # 输出为2个隐藏层和输出层
        return fc1, fc2, output


# 网络结构传播过程的可视化结构
mlpc = MLPclassifica()
x = torch.randn(1, 57).requires_grad_(True)
y = mlpc(x)
Mymlpcvis = make_dot(y, params=dict(list(mlpc.named_parameters()) + [('x', x)]))
Mymlpcvis.format = "png"
Mymlpcvis.directory = "d:/Users/17248/Desktop/MLP2.png"
Mymlpcvis.view()

##############################3、使用未进行标准化预处理的数据训练模型###########################
##############################将X_train_s换成X_train,X_test_s换成X_test###########################
# 将数据转化为张量
X_train_t = torch.from_numpy(X_train.astype(np.float32))
Y_train_t = torch.from_numpy(Y_train.astype(np.int64))
X_test_t = torch.from_numpy(X_test.astype(np.float32))
Y_test_t = torch.from_numpy(Y_test.astype(np.int64))
# 将X和Y整理到一起
train_data = Data.TensorDataset(X_train_t, Y_train_t)
# 定义数据加载器，将训练数据集进行批量处理
train_loader = Data.DataLoader(
    dataset=train_data,  # 所使用的数据集
    batch_size=64,  # 批处理的大小
    shuffle=True,  # 每次迭代前打乱数据
    # num_workers=1,    #=1代表2个线程读取
)

# 定义优化器
optimizer = torch.optim.Adam(mlpc.parameters(), lr=0.01)  # 待优化参数为步骤2中的全连接神经网络的参数，算法学习率为0.01
loss_func = nn.CrossEntropyLoss()  # 二分类损失函数
history1 = hl.History()  # 记录训练过程的指标
canvas1 = hl.Canvas()  # 使用Canvas（）进行可视化
print_step = 25  # 每经过25次迭代后 输出损失

# 对模型进行迭代训练，对所有的数据训练epoch轮
for epoch in range(15):
    for step, (b_x, b_y) in enumerate(train_loader):  # 对训练数据的加载器进行迭代计算
        # 计算每个batch的损失
        _, _, output = mlpc(b_x)  # mlp在训练batch上的输出
        train_loss = loss_func(output, b_y)  # 二分类交叉熵损失函数
        optimizer.zero_grad()  # 每个迭代步的梯度初始化为0
        train_loss.backward()  # 损失函数后向传播，计算梯度
        optimizer.step()  # 使用梯度进行优化
        niter = epoch * len(train_loader) + step + 1
        # 计算每经过print_step次迭代后的输出
        if niter % print_step == 0:
            _, _, output = mlpc(X_test_t)
            _, pre_lab = torch.max(output, 1)  # 用于计算每个样本预测得到的类别；dim=1代表输出行的最大值，行=样本
            test_accuracy = accuracy_score(Y_test_t, pre_lab)
            # 为history添加epoch，损失和精度
            history1.log(niter, train_loss=train_loss, test_accuracy=test_accuracy)
            # 使用两个图像可视化损失函数和精度
            with canvas1:
                canvas1.draw_plot(history1["train_loss"])
                canvas1.draw_plot(history1["test_accuracy"])
# 计算模型在测试集上的最终精度
_, _, output = mlpc(X_test_t)
_, pre_lab = torch.max(output, 1)
test_accuracy = accuracy_score(Y_test_t, pre_lab)
classifica_report = classification_report(Y_test_t, pre_lab, target_names=['正常邮件', '垃圾邮件'])
print("测试集分类精度:", test_accuracy)
print(classifica_report)
